﻿using AssetRipper.Assets.Generics;
using AssetRipper.Numerics;
using AssetRipper.SourceGenerated.Classes.ClassID_213;
using AssetRipper.SourceGenerated.Classes.ClassID_687078895;
using AssetRipper.SourceGenerated.Enums;
using AssetRipper.SourceGenerated.Subclasses.SpriteAtlasData;
using AssetRipper.SourceGenerated.Subclasses.SpriteBone;
using AssetRipper.SourceGenerated.Subclasses.SpriteMetaData;
using AssetRipper.SourceGenerated.Subclasses.SpriteRenderData;
using AssetRipper.SourceGenerated.Subclasses.SpriteVertex;
using AssetRipper.SourceGenerated.Subclasses.SubMesh;
using AssetRipper.SourceGenerated.Subclasses.Vector2f;
using System.Buffers.Binary;
using System.Drawing;
using System.Numerics;

namespace AssetRipper.SourceGenerated.Extensions;

public static class SpriteMetaDataExtensions
{
	public static SpriteAlignment GetAlignment(this ISpriteMetaData data)
	{
		return (SpriteAlignment)data.Alignment;
	}

	public static void FillSpriteMetaData(this ISpriteMetaData instance, ISprite sprite, ISpriteAtlas? atlas)
	{
		sprite.GetSpriteCoordinatesInAtlas(atlas, out RectangleF rect, out Vector2 pivot, out Vector4 border);
		instance.Name = sprite.Name;
		instance.Rect.CopyValues(rect);
		instance.Alignment = (int)SpriteAlignment.Custom;
		instance.Pivot.CopyValues(pivot);
		instance.Border?.CopyValues(border);
		if (instance.Has_Outline())
		{
			GenerateOutline(sprite, atlas, rect, pivot, instance.Outline);
		}
		if (instance.Has_PhysicsShape() && sprite.Has_PhysicsShape())
		{
			GeneratePhysicsShape(sprite, atlas, rect, pivot, instance.PhysicsShape);
		}
		instance.TessellationDetail = 0;
		if (instance.Has_Bones() && sprite.Has_Bones() && instance.Has_SpriteID())
		{
			// Scale bones based off of the sprite's PPU
			foreach (ISpriteBone bone in sprite.Bones)
			{
				bone.Position.Scale(sprite.PixelsToUnits);
				bone.Length *= sprite.PixelsToUnits;

				// Set root bone position
				if (bone.ParentId == -1)
				{
					bone.Position.X += sprite.Rect.Width / 2;
					bone.Position.Y += sprite.Rect.Height / 2;
				}
			}

			instance.Bones.Clear();
			instance.Bones.Capacity = sprite.Bones.Count;
			foreach (ISpriteBone bone in sprite.Bones)
			{
				instance.Bones.AddNew().CopyValues(bone);
			}

			// NOTE: sprite ID is generated by sprite binary content, but we just generate a random value
			instance.SpriteID = Guid.NewGuid().ToString("N");

			instance.SetBoneGeometry(sprite);
		}
	}

	private static void SetBoneGeometry(this ISpriteMetaData instance, ISprite origin)
	{
		Vector3[]? vertices = null;
		BoneWeight4[]? skin = null;

		if (origin.RD.Has_VertexData())
		{
			VertexDataBlob.Create(origin.RD.VertexData, origin.Collection.Version, origin.Collection.EndianType).ReadData(
				out vertices,
				out Vector3[]? _,//normals,
				out Vector4[]? _,//tangents,
				out ColorFloat[]? _,//colors,
				out Vector2[]? _,//uv0,
				out Vector2[]? _,//uv1,
				out Vector2[]? _,//uv2,
				out Vector2[]? _,//uv3,
				out Vector2[]? _,//uv4,
				out Vector2[]? _,//uv5,
				out Vector2[]? _,//uv6,
				out Vector2[]? _,//uv7),
				out skin);
		}

		if (instance.Has_Vertices())
		{
			instance.Vertices.Clear();

			// Convert Vector3f into Vector2f
			if (vertices is null)
			{
				instance.Vertices.Capacity = 0;
			}
			else
			{
				instance.Vertices.Capacity = vertices.Length;
				for (int i = 0; i < vertices.Length; i++)
				{
					Vector2f vertex = instance.Vertices.AddNew();

					// Scale and translate vertices properly
					vertex.X = vertices[i].X * origin.PixelsToUnits + origin.Rect.Width / 2;
					vertex.Y = vertices[i].Y * origin.PixelsToUnits + origin.Rect.Height / 2;
				}
			}
		}

		if (instance.Has_Indices())
		{
			instance.Indices.Clear();
			if (origin.RD.Has_IndexBuffer() && origin.RD.IndexBuffer.Length != 0)
			{
				instance.Indices.Capacity = origin.RD.IndexBuffer.Length / 2;
				for (int i = 0, j = 0; i < origin.RD.IndexBuffer.Length / 2; i++, j += 2)
				{
					//Endianness might matter here
					instance.Indices.Add(BinaryPrimitives.ReadInt16LittleEndian(origin.RD.IndexBuffer.AsSpan(j, 2)));
				}
			}
		}

#warning TODO: SpriteConverter does not generate instance.Edges

		if (instance.Has_Weights())
		{
			instance.Weights.Clear();
			if (skin is not null)
			{
				instance.Weights.EnsureCapacity(skin.Length);
				for (int i = 0; i < skin.Length; i++)
				{
					instance.Weights.AddNew().CopyValues(skin[i]);
				}
			}
		}
	}

	private static void GeneratePhysicsShape(
		ISprite sprite,
		ISpriteAtlas? atlas,
		RectangleF rect,
		Vector2 pivot,
		AssetList<AssetList<Vector2f>> shape)
	{
		if (sprite.Has_PhysicsShape() && sprite.PhysicsShape.Count > 0)
		{
			shape.Clear();
			shape.Capacity = sprite.PhysicsShape.Count;
			float pivotShiftX = rect.Width * pivot.X - rect.Width * 0.5f;
			float pivotShiftY = rect.Height * pivot.Y - rect.Height * 0.5f;
			Vector2 pivotShift = new Vector2(pivotShiftX, pivotShiftY);
			for (int i = 0; i < sprite.PhysicsShape.Count; i++)
			{
				AssetList<Vector2f> sourceList = sprite.PhysicsShape[i];
				AssetList<Vector2f> targetList = shape.AddNew();
				targetList.Capacity = sourceList.Count;
				for (int j = 0; j < sprite.PhysicsShape[i].Count; j++)
				{
					Vector2 point = (Vector2)sourceList[j] * sprite.PixelsToUnits;
					targetList.AddNew().CopyValues(point + pivotShift);
				}
			}
			shape.FixRotation(sprite, atlas);
		}
	}

	private static void FixRotation(this AssetList<AssetList<Vector2f>> outlines, ISprite sprite, ISpriteAtlas? atlas)
	{
		GetPacking(sprite, atlas, out bool isPacked, out SpritePackingRotation rotation);

		if (isPacked)
		{
			switch (rotation)
			{
				case SpritePackingRotation.FlipHorizontal:
					{
						foreach (AssetList<Vector2f> outline in outlines)
						{
							for (int i = 0; i < outline.Count; i++)
							{
								Vector2f vertex = outline[i];
								outline[i].SetValues(-vertex.X, vertex.Y);
							}
						}
					}
					break;

				case SpritePackingRotation.FlipVertical:
					{
						foreach (AssetList<Vector2f> outline in outlines)
						{
							for (int i = 0; i < outline.Count; i++)
							{
								Vector2f vertex = outline[i];
								outline[i].SetValues(vertex.X, -vertex.Y);
							}
						}
					}
					break;

				case SpritePackingRotation.Rotate90:
					{
						foreach (AssetList<Vector2f> outline in outlines)
						{
							for (int i = 0; i < outline.Count; i++)
							{
								Vector2f vertex = outline[i];
								outline[i].SetValues(vertex.Y, vertex.X);
							}
						}
					}
					break;

				case SpritePackingRotation.Rotate180:
					{
						foreach (AssetList<Vector2f> outline in outlines)
						{
							for (int i = 0; i < outline.Count; i++)
							{
								Vector2f vertex = outline[i];
								outline[i].SetValues(-vertex.X, -vertex.Y);
							}
						}
					}
					break;
			}
		}
	}

	/// <summary>
	/// Pure
	/// </summary>
	/// <param name="sprite"></param>
	/// <param name="atlas"></param>
	/// <param name="isPacked"></param>
	/// <param name="rotation"></param>
	private static void GetPacking(ISprite sprite, ISpriteAtlas? atlas, out bool isPacked, out SpritePackingRotation rotation)
	{
		if (atlas is not null && sprite.Has_RenderDataKey() && atlas.RenderDataMap.TryGetValue(sprite.RenderDataKey, out ISpriteAtlasData? atlasData))
		{
			isPacked = atlasData.IsPacked;
			rotation = atlasData.PackingRotation;
		}
		else
		{
			isPacked = sprite.RD.IsPacked;
			rotation = sprite.RD.PackingRotation;
		}
	}

	private static void GenerateOutline(
		ISprite sprite,
		ISpriteAtlas? atlas,
		RectangleF rect,
		Vector2 pivot,
		AssetList<AssetList<Vector2f>> outlines)
	{
		GenerateOutline(sprite.RD, sprite.Collection.Version, outlines);
		float pivotShiftX = rect.Width * pivot.X - rect.Width * 0.5f;
		float pivotShiftY = rect.Height * pivot.Y - rect.Height * 0.5f;
		Vector2 pivotShift = new Vector2(pivotShiftX, pivotShiftY);
		foreach (AssetList<Vector2f> outline in outlines)
		{
			for (int i = 0; i < outline.Count; i++)
			{
				Vector2 point = (Vector2)outline[i] * sprite.PixelsToUnits;
				outline[i].CopyValues(point + pivotShift);
			}
		}
		outlines.FixRotation(sprite, atlas);
	}

	private static void GenerateOutline(
		ISpriteRenderData spriteRenderData,
		UnityVersion version,
		AssetList<AssetList<Vector2f>> outlines)
	{
		outlines.Clear();
		if (spriteRenderData.Has_VertexData() && spriteRenderData.SubMeshes!.Count != 0)
		{
			for (int i = 0; i < spriteRenderData.SubMeshes.Count; i++)
			{
				Vector3[] vertices = spriteRenderData.VertexData.GenerateVertices(version, spriteRenderData.SubMeshes[i]);
				List<Vector2[]> vectorArrayList = VertexDataToOutline(spriteRenderData.IndexBuffer, vertices, spriteRenderData.SubMeshes[i]);
				outlines.AddRanges(vectorArrayList);
			}
		}
		else if (spriteRenderData.Has_Vertices() && spriteRenderData.Vertices.Count != 0)
		{
			List<Vector2[]> vectorArrayList = VerticesToOutline(spriteRenderData.Vertices, spriteRenderData.Indices);
			outlines.Capacity = vectorArrayList.Count;
			outlines.AddRanges(vectorArrayList);
		}
	}

	private static List<Vector2[]> VerticesToOutline(AccessListBase<ISpriteVertex> spriteVertexList, AssetList<ushort> spriteIndexArray)
	{
		Vector3[] vertices = new Vector3[spriteVertexList.Count];
		for (int i = 0; i < vertices.Length; i++)
		{
			vertices[i] = spriteVertexList[i].Pos;
		}

		Vector3i[] triangles = new Vector3i[spriteIndexArray.Count / 3];
		for (int i = 0, j = 0; i < triangles.Length; i++)
		{
			int x = spriteIndexArray[j++];
			int y = spriteIndexArray[j++];
			int z = spriteIndexArray[j++];
			triangles[i] = new Vector3i(x, y, z);
		}

		return new MeshOutlineGenerator(vertices, triangles).GenerateOutlines();
	}

	private static List<Vector2[]> VertexDataToOutline(ReadOnlySpan<byte> indexBuffer, Vector3[] vertices, ISubMesh submesh)
	{
		Vector3i[] triangles = new Vector3i[submesh.IndexCount / 3];
		for (int o = (int)submesh.FirstByte, ti = 0; ti < triangles.Length; o += 3 * sizeof(ushort), ti++)
		{
			ushort x = BinaryPrimitives.ReadUInt16LittleEndian(indexBuffer[o..]);
			ushort y = BinaryPrimitives.ReadUInt16LittleEndian(indexBuffer[(o + sizeof(ushort))..]);
			ushort z = BinaryPrimitives.ReadUInt16LittleEndian(indexBuffer[(o + 2 * sizeof(ushort))..]);
			triangles[ti] = new Vector3i(x, y, z);
		}

		return new MeshOutlineGenerator(vertices, triangles).GenerateOutlines();
	}

	private static void AddRanges(this AssetList<AssetList<Vector2f>> instance, List<Vector2[]> vectorArrayList)
	{
		foreach (Vector2[] vectorArray in vectorArrayList)
		{
			AssetList<Vector2f> assetList = instance.AddNew();
			assetList.Capacity = vectorArray.Length;
			foreach (Vector2 v in vectorArray)
			{
				assetList.AddNew().CopyValues(v);
			}
		}
	}
}
